using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BRTDriver : MonoBehaviour
{
    [Header("Loader settings")]
    [Tooltip("This is the target avatar for which the animation should be loaded. Bone names should be identical to those in the BVH file and unique. All bones should be initialized with zero rotations. This is usually the case for VRM avatars.")]
    public Animator targetAvatar;

    public string filename = "";

    //[Serializable]
    //public struct BoneMap
    //{
    //    public string name;
    //    public HumanBodyBones humanoid_bone;
    //}

    [Serializable]
    public struct BoneMap
    {
        public string lname;
        public string rname;
    }

    private BoneMap[] bmapsALL;
    private BoneMap[] bmapsBRT;
    private BoneMap[] bmapsNSM;
    private BoneMap[] bmapsNSM_all;

    //private BoneMap[] bmapsALL; // the corresponding bones between unity and bvh
    //private BoneMap[] bmapsBVH; // the corresponding bones between unity and bvh
    //private BoneMap[] bmapsNSM; // the corresponding bones between unity and nsm

    private Animator anim;
    private Dictionary<string, Quaternion> unityT;
    private Dictionary<string, Quaternion> brtT;
    private string searchKey = "";
    private static int frameIdx = 0;

    public class frame
    {
        public Dictionary<string, Vector3> euler;
        public Dictionary<string, Quaternion> localRotation;
        //public Dictionary<string, Quaternion> rotation;
    }
    public List<frame> frames = new List<frame>();
    public frame tpose_frame = new frame();

    public void searchInit()
    {
        getFrames(filename);

        anim = targetAvatar.GetComponent<Animator>();
        unityT = new Dictionary<string, Quaternion>(); // Tpose global rotation

        foreach (Transform child in GetComponentsInChildren<Transform>())
        {
            Quaternion rot = child.rotation;
            string name = child.name;
            string[] tn = name.Split(':');
            if (tn.Length <= 1)
                continue;
            name = tn[1];
            unityT.Add(name, rot);
        }
    }

    public void searchMotion(string skey)
    {
        // search
        if (!searchKey.Equals(skey))
        {
            searchKey = skey;
            filename = skey;
            if (searchKey == "")
                return;

            // search motion lib by key to get filename
            searchInit();  // initial BVHParser bp            
        }
    }
    // read file brt
    public void getFrames(string filename)
    {
        using (StreamReader sr = new StreamReader(@"Assets\Resources\Brt\" + filename + ".brt"))
        {
            string line = sr.ReadLine();
            int bonesCount = Int32.Parse(line.Split(':')[1]);

            if (sr.ReadLine().Equals("Tpose:"))
            {
                Dictionary<string, Vector3> dv = new Dictionary<string, Vector3>();
                Dictionary<string, Quaternion> dq = new Dictionary<string, Quaternion>();
                for (int i = 0; i < bonesCount; i++)
                {
                    line = sr.ReadLine();
                    string[] tl = line.Split(' ');
                    if (tl.Length != 4)
                        continue;

                    Vector3 tv = new Vector3(float.Parse(tl[1]), float.Parse(tl[2]), float.Parse(tl[3]));
                    dv.Add(tl[0], tv);
                    dq.Add(tl[0], Quaternion.EulerAngles(tv));
                }
                tpose_frame.euler = dv;
                tpose_frame.localRotation = dq;
            }

            brtT = getFrame();

            while ((line = sr.ReadLine()) != null)
            {
                int idx = Int32.Parse(line.Split(':')[1]);

                frame tf = new frame();
                Dictionary<string, Vector3> te = new Dictionary<string, Vector3>();
                Dictionary<string, Quaternion> tr = new Dictionary<string, Quaternion>();

                for (int i = 0; i < bonesCount; i++)
                {
                    string[] info = sr.ReadLine().Split(' ');
                    if (info.Length != 4)
                        continue;

                    Vector3 tv = new Vector3(float.Parse(info[1]), float.Parse(info[2]), float.Parse(info[3]));

                    te.Add(info[0], tv);
                    tr.Add(info[0], Quaternion.EulerAngles(tv));
                }
                tf.euler = te;
                tf.localRotation = tr;
                frames.Add(tf);
            }
        }
    }

    private string toJoint(string name)
    {
        string[] tn = name.Split(':');
        if (tn.Length == 2)
            return tn[1];
        return null;
    }
    private Transform getTransformByName(string name)
    {
        foreach (Transform child in GetComponentsInChildren<Transform>())
        {
            string child_name = toJoint(child.name);
            if (child_name != null && name.Equals(child_name))
                return child;
        }
        return null;
    }

    public void updateRotations(Dictionary<string, Quaternion> nsmRots, Vector3 rpos)
    {

        if (frameIdx < frames.Count - 5)
            frameIdx += 1;
        else
            frameIdx = 5;

        if (filename == "")
        {
            foreach (BoneMap bm in bmapsNSM_all)
            {
                Transform currBone = getTransformByName(bm.rname);
                currBone.rotation = nsmRots[bm.lname];
            }
        } 
        else
        {
            Quaternion rrot = new Quaternion();
            foreach (BoneMap bm in bmapsNSM) // Compute global rotations
            {
                Transform currBone = getTransformByName(bm.rname);
                currBone.rotation = nsmRots[bm.lname] * unityT[bm.rname];
                if (bm.lname == "Hips")
                {
                    rrot = currBone.rotation;
                    Debug.Log(rrot);
                }
            }

            foreach (BoneMap bm in bmapsBRT)
            {
                Transform currBone = getTransformByName(bm.rname);
                // brt
                //child.transform.rotation = currFrame[name] * Quaternion.Inverse(brtT[name]) * unityT[name];
                //child.transform.rotation = currFrame[name];
                currBone.localRotation = frames[frameIdx].localRotation[bm.lname];
                currBone.rotation = currBone.rotation;
            }
        }

        // set root rotation
        Vector3 anim_rpos = new Vector3(rpos.x, rpos.y, rpos.z);
        //anim_rpos.y = getTransformByName("Hips").position.y;
        getTransformByName("Hips").position = anim_rpos;
    }

    private void Start()
    {
        BonemapsReader reader = new BonemapsReader();

        //reader.Read(Params.bonesMapPath + "Bonesmap.txt");
        //bmapsALL = reader.bonemaps;

        reader.Read(Params.bonesMapPath + "map_brt.txt");
        bmapsBRT = reader.bonemaps;

        reader.Read(Params.bonesMapPath + "map_nsm.txt");
        bmapsNSM = reader.bonemaps;

        reader.Read(Params.bonesMapPath + "map_nsm_all.txt");
        bmapsNSM_all = reader.bonemaps;
    }

    public Dictionary<string, string> getHierachy()
    {
        Dictionary<string, string> hierachy = new Dictionary<string, string>();
        foreach (Transform joint in GetComponentsInChildren<Transform>())
        {
            string joint_name;
            string[] jns = joint.name.Split(':');
            if (jns.Length != 2)
                continue;
            joint_name = jns[1];

            foreach (Transform child in joint) 
            {
                string child_name;
                string[] cns = child.name.Split(':');

                if (cns.Length != 2)
                    continue;
                child_name = cns[1];

                hierachy.Add(child_name, joint_name);
            }
        }
        return hierachy;
    }

    public Dictionary<string, Quaternion> getFrame(int idx)
    {
        Dictionary<string, string> hierachy = getHierachy();
        Dictionary<string, Quaternion> boneData = new Dictionary<string, Quaternion>();

        boneData.Add("Hips", Quaternion.Euler(new Vector3(0f,0f,0f)));

        foreach (Transform joint in GetComponentsInChildren<Transform>())
        {
            string joint_name = toJoint(joint.name);
            if (joint_name != null)
            {
                if (!joint_name.Equals("Hips"))
                {
                    Quaternion localRot;
                    if (frames[idx].localRotation.ContainsKey(joint_name))
                    {
                        localRot = frames[idx].localRotation[joint_name];
                        if (hierachy.ContainsKey(joint_name) && boneData.ContainsKey(hierachy[joint_name]))
                            boneData.Add(joint_name, boneData[hierachy[joint_name]] * localRot);
                    }
                }
            }
        }

        return boneData;
    }

    public Dictionary<string, Quaternion> getFrame()
    {
        Dictionary<string, string> hierachy = getHierachy();
        Dictionary<string, Quaternion> boneData = new Dictionary<string, Quaternion>();

        boneData.Add("Hips", tpose_frame.localRotation["Hips"]);
        //boneData.Add("Hips", hipsRot);

        foreach (Transform joint in GetComponentsInChildren<Transform>())
        {
            string joint_name;
            string[] jns = joint.name.Split(':');
            if (jns.Length != 2)
                continue;
            joint_name = jns[1];

            Quaternion localRot;
            if (tpose_frame.localRotation.ContainsKey(joint_name))
            {
                localRot = tpose_frame.localRotation[joint_name];
                if (hierachy.ContainsKey(joint_name) && boneData.ContainsKey(hierachy[joint_name]))
                    boneData.Add(joint_name, boneData[hierachy[joint_name]] * localRot);
            }
        }

        return boneData;
    }
}